package fr.cephb.locustree;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.InvalidPropertiesFormatException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;

import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;

import fr.cephb.berkeleydb.db.Walker;
import fr.cephb.json.JSONBuilder;
import fr.cephb.json.JSONable;
import fr.cephb.locustree.svg.TrackHandler;
import fr.cephb.util.Cast;
import fr.cephb.util.StringUtils;
import fr.cephb.util.XMLBeanFactory;
import fr.cephb.xml.SVG;

/**
 * LTServlet
 * @author pierre
 *
 */
public class LTServlet extends HttpServlet
	{
	private static final long serialVersionUID = 1L;
	
	final static private String CONFIG_RSRC="/WEB-INF/locustree.properties.xml";

	private String password=null;
	private String login=null;
	private LTModel ltModel=null;
	private XMLBeanFactory beanFactory=null;


	/**
	 * TrackChoice
	 */
	static public class TrackChoice
	implements JSONable,Serializable
		{
		private static final long serialVersionUID = 1L;
		private Integer trackId;
		private boolean selected=true;
		private String stroke="black";
		private String fill="white";
		private int strokeWidth=1;
		public Integer getTrackId()
			{
			return trackId;
			}
		public boolean isSelected()
			{
			return selected;
			}
		public String getStroke()
			{
			return this.stroke;
			}
		
		public String getFill()
			{
			return fill;
			}
		
		public int getStrokeWidth()
			{
			return strokeWidth;
			}
		
		@Override
		public String toJSON()
			{
			return new JSONBuilder().
				put("track-id",getTrackId()).
				put("selected",isSelected()).
				put("stroke",getStroke()).
				put("fill",getFill()).
				put("strokeWidth",getStrokeWidth()).
				getString();
			}
		@Override
		public String toString()
			{
			return toJSON();
			}
		}	
	
	/**
	 * User
	 */
	static public class User
		implements JSONable,Serializable
		{
		private static final long serialVersionUID = 1L;
		private Integer organism_id=null;
		private Map<Integer,TrackChoice> trackChoices=new HashMap<Integer, TrackChoice>();
		public User()
			{
			
			}
		public Integer getOrganismId()
			{
			return organism_id;
			}
		public Map<Integer, TrackChoice> getTrackPrefs()
			{
			return trackChoices;
			}
		
		@Override
		public String toJSON()
			{
			return new JSONBuilder().
				put("organism-id",getOrganismId()).
				put("tracks",getTrackPrefs()).
				getString();
			}
		
		@Override
		public String toString()
			{
			return toJSON();
			}
		}
	
	
	
	
	@Override
	public void init() throws ServletException
		{
		
		InputStream in=getServletContext().getResourceAsStream(CONFIG_RSRC);
		if(in==null)
			{
			throw new ServletException("Cannot get the config file \""+CONFIG_RSRC+"\"");
			}
		String dbHome=null;
		Properties props=new Properties();
		try
			{
			props.loadFromXML(in);
			in.close();
			this.password=props.getProperty("password");
			this.login=props.getProperty("login");
			dbHome=props.getProperty("db-home");
			if(dbHome==null || dbHome.trim().length()==0) throw new ServletException("Cannot property db-home "+CONFIG_RSRC);
			String beansFile= props.getProperty("beans");
			if(beansFile!=null &&
				!beansFile.trim().isEmpty())
				
				{
				try
					{
					this.beanFactory=XMLBeanFactory.loadInstance(new File(beansFile));
					}
				catch (Exception e)
					{
					log(e.getMessage(),e);
					}
				}
			}
		catch (InvalidPropertiesFormatException e)
			{
			throw new ServletException(e);
			}
		catch (IOException e)
			{
			throw new ServletException(e);
			}
		log("Opening LTModel "+dbHome);
		this.ltModel=new LTModel();
		this.ltModel.open(new File(dbHome), false);
		}
	
	@Override
	public void destroy()
		{
		if(this.ltModel!=null)
			{
			try
				{
				log("Closing LTModel");
				this.ltModel.close();
				}
			catch (Exception e)
				{
				log("Error closing model ",e);
				}
			this.ltModel=null;
			}
		super.destroy();
		}
	
	public LTModel getModel()
		{
		return ltModel;
		}
	
	public Transaction getTransaction()
		{
		return null;
		}
	
	@Override
	protected void service(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException
		{
		/* redirect to soap */
		if( req.getServletPath()!=null)
			{
			if(req.getServletPath().endsWith("/locustree/soap"))
				{
				req.setAttribute("model", getModel());
				req.getRequestDispatcher(req.getServletPath().substring(10)).forward(req,res);
				return;
				}
			}
		
		
		String action=req.getParameter("action");
		HttpSession session= req.getSession(false);
		if(action==null)
			{
			if(session==null && this.password==null && this.login!=null)
				{
				jspDispatch("login",req,res);
				return;
				}
			action="";
			}
		else if(action.equals("login"))
			{
			if(session!=null) session.invalidate();
			session=null;
			jspDispatch("login",req,res);
			return;
			}
		else if(action.equals("validate"))
			{
			if(session!=null) session.invalidate();
			session=null;
			if(this.password!=null && this.login!=null)
				{
				if(!(this.login.equals(req.getParameter("login")) &&
					 this.password.equals(req.getParameter("password")))
					 )
					{
					
					jspDispatch("login",req,res);
					return;
					}
				
				}
			session= req.getSession(true);
			}

		if(session==null )
			{
			if(this.login!=null && this.password!=null)
				{
				
				jspDispatch("login",req,res);
				return;
				}
			session= req.getSession(true);
			}
		
	
		User user=User.class.cast(session.getAttribute("user"));
		if(user==null)
			{
			user= createUser();
			session.setAttribute("user", user);
			}
		
		/*log("##LOG\npi="+ req.getPathInfo()+
			"\nqs:"+req.getQueryString()+
			"\nrequri:"+req.getRequestURI()+
			"\ncp:"+req.getContextPath()+
			"\nsp:"+req.getServletPath()+
			"\nsn:"+getServletName()+
			"\ntr:"+req.getPathTranslated()
			);*/
		
		if( req.getServletPath()!=null &&
			req.getServletPath().endsWith("/das")
			)
			{
			runDasProtocol(req, res);
			return;
			}
		
		if(action.equals("chooseorg"))
			{
			Integer orgid= Cast.Integer.cast(req.getParameter("organism-id"));
			if(orgid==null|| !getModel().getOrganismDB().containsKey(getTransaction(), orgid))
				{
				jspDispatch("organisms",req,res);
				return;
				}
			user.organism_id=orgid;
			jspDispatch("browse",req,res);
			return;
			}
		
		if(user.getOrganismId()==null)
			{
			jspDispatch("organisms",req,res);
			return;
			}
		if(action.equals("export"))
			{
			boolean compressed_response= !StringUtils.isEmpty(req.getParameter("gz"));
			boolean download_response= !StringUtils.isEmpty(req.getParameter("download"));
			
			
			ChromInfo ci=null;
			
			int start=0;
			int end=0;
			
			Integer orgid= Cast.Integer.cast(req.getParameter("organism-id"));
			if(orgid==null) orgid= user.getOrganismId();
			Organism organism= getModel().getOrganismDB().get(getTransaction(), orgid);
			if(organism==null)
				{
				throw new ServletException("Unknown organism: "+orgid);
				}
			
			if(!StringUtils.isEmpty(req.getParameter("query")))
				{
				String query=req.getParameter("query").trim();
				int i=query.indexOf(':');
				String s1= (i==-1?query.trim():query.substring(0, i));
				ci= getModel().getChromsomeByName(getTransaction(),orgid,s1);
				if(ci==null) throw new ServletException("unknown chromosome : "+s1);
				end=ci.getLength();
				
				if(i!=-1)
					{
					s1= query.substring(i+1).trim();
					
					i=s1.indexOf('-');
					String s2= (i==-1?s1.trim():s1.substring(0, i));
					s2=s2.replace(",", "").trim();
					if(!StringUtils.isEmpty(s2))
						{
						if(!Cast.NonNegativeInteger.isA(s2)) throw new ServletException("bad start : "+s2);
						start=Integer.parseInt(s2);
						}
					if(i!=-1)
						{
						s2= s1.substring(i+1);
						s2=s2.replace(",", "").trim();
						if(!StringUtils.isEmpty(s2))
							{
							if(!Cast.NonNegativeInteger.isA(s2)) throw new ServletException("bad end : "+s2);
							end=Integer.parseInt(s2);
							}
						}
					}
				}
			else
				{
				Integer chrom=Cast.IntegerGreaterThanZero.cast(req.getParameter("chrom"));
				if(chrom==null) throw new ServletException("Bad chromosome.");
				 ci= getModel().getChromDB().get(getTransaction(), chrom);
				if(ci==null) throw new ServletException("unknown chromosome : "+chrom);
				start=parseUInt(req.getParameter("start"),0);
				end=parseUInt(req.getParameter("end"),ci.getLength());
				}
			
			if(end>ci.getLength()) end=ci.getLength();
			if(start>end) throw new ServletException("start>end");
			if(start==end) end++;
			int width= parseUInt(req.getParameter("width"), 700);
			if(width<10) width=700;
			if(width>2000) width=2000;
			
			
			String mode= req.getParameter("mode");
			
			
			/* EXPORT AS TEXT */
			if(mode.equals("txt"))
				{
				if(download_response)
					{
					res.setHeader("Content-Disposition", "attachment;filename="+
						 organism.getName()+"_"+ci.getName()+"_"+start+"_"+end+
						".txt"+ (compressed_response?".gz":"")
						);
					}
				
				res.setContentType(
						compressed_response?
						"application/octet-stream":
						"text/plain"
						);
					
				
				OutputStream io=res.getOutputStream();
				GZIPOutputStream zout= null;
				if(compressed_response)
					{
					zout= new GZIPOutputStream(io);
					io=zout;
					}
				PrintStream out= new PrintStream(io);
				Walker<Integer,Track> w=null;
				try
					{
					w=getModel().getTracksDB().openWalker(getTransaction());
					while(w.getNext()==OperationStatus.SUCCESS)
						{
						Long nodeid=getModel().getTrackChrom2NodeId().get(
								getTransaction(),
								new TrackChrom(w.getKey(), ci.getId()));
						if(nodeid==null || nodeid==LTNode.NIL) continue;
						Track track=w.getValue();
						dump(out,nodeid,organism,track,ci,start,end);
						}
					if(zout!=null)
						{
						zout.finish();
						}
					out.flush();
					out.close();
					return;
					}
				catch(Throwable err)
					{
					throw new ServletException(err);
					}
				finally
					{
					w.close();
					}
				}
			else /* EXPORT AS SVG */
				{
				LTSvg processor=new LTSvg(
						getModel(),
						organism,
						ci, 
						start, 
						(end-start)
						);
				if(this.beanFactory!=null &&
					this.beanFactory.containsBean("track-handlers"))
					{
					Map<?,?> ppty= this.beanFactory.getBean("track-handlers",Map.class);
					for(Object k:ppty.keySet())
						{
						if(k==null || !(k instanceof Integer)) continue;
						Object value=ppty.get(k);
						if(value==null || !(value instanceof TrackHandler)) continue;
						processor.setTrackHandler(
							Integer.class.cast(k).intValue(),
							TrackHandler.class.cast(value)
							);
						}
					}
				processor.setPaneWidth(width);
				Walker<Integer,Track> w=null;
				try
					{
					w=getModel().getTracksDB().openWalker(getTransaction());
					while(w.getNext()==OperationStatus.SUCCESS)
						{
						if(!getModel().getTrackChrom2NodeId().containsKey(getTransaction(),
								new TrackChrom(w.getKey(), ci.getId())))
							{
							continue;
							}
						Track track=w.getValue();
						String checked= req.getParameter("displayTrack["+track.getId()+"]");
						if("hide".equals(checked)) continue;
						processor.scanTrack(getTransaction(),track);
						}
					}
				catch(Throwable err)
					{
					throw new ServletException(err);
					}
				finally
					{
					w.close();
					}
				res.setContentType(
						compressed_response?
						"application/octet-stream":
						SVG.MIME_TYPE
						);
				
				res.setHeader("Content-Disposition",
						(download_response?"attachment":"inline")+
						";filename="+
						 organism.getName()+"_"+ci.getName()+"_"+start+"_"+end+
						".svg"+(compressed_response?"z":""));
				OutputStream out= res.getOutputStream();
				GZIPOutputStream zout= null;
				if(compressed_response)
					{
					zout=new GZIPOutputStream(out);
					out=zout;
					}
				processor.printSVGDocument(out);
				if(zout!=null)
					{
					zout.finish();
					}
				out.flush();
				out.close();
				}
			return;
			}
		if(action.equals("search"))
			{
			String q= req.getParameter("q");
			if(!StringUtils.isEmpty(q))
				{
				final long timeout=40*1000;//40 secs
				q=q.trim();
				SearchHandler handler= new SearchHandler();
				handler.setModel(getModel());
				handler.setTimeout(timeout);
				handler.setTransaction(getTransaction());
				handler.setRegularExpression(!StringUtils.isEmpty(req.getParameter("regex")));
				handler.setCaseSensible(!StringUtils.isEmpty(req.getParameter("casei")));
				handler.setWholeWord(!StringUtils.isEmpty(req.getParameter("whole")));
				handler.setLimit(parseUInt(req.getParameter("limit"), 10));
				
				
				handler.search(q);
				if(handler.isLastQueryTimedOut())
					{
					req.setAttribute("timeout",timeout);
					}
				req.setAttribute("hits", handler.hits);
				jspDispatch("search",req,res);
				return;
				}
			jspDispatch("search",req,res);
			return;
			}
		
		jspDispatch("organisms",req,res);
		}
	
	public static class SearchHit
		{
		long nodeId;
		MappedObject mapped;
		Track track;
		ChromInfo chrom;
		Organism organism;
		
		public MappedObject getObject()
			{
			return mapped;
			}
		public long getNodeId()
			{
			return nodeId;
			}
		public ChromInfo getChromosome()
			{
			return this.chrom;
			}
		public Track getTrack()
			{
			return this.track;
			}
		public Organism getOrganism()
			{
			return this.organism;
			}
		public String getMetadataAsJson()
			{
			if(this.mapped.getJSON()==null) return "";
			StringWriter s= new StringWriter();
			try
				{
				new JSONBuilder().write(s, this.mapped.getJSON());
				}
			catch (IOException e)
				{
				throw new RuntimeException(e);
				}
			return s.toString();
			}
		}
	
	/**
	 * 
	 * SearchHandler
	 *
	 */
	private static class SearchHandler
		extends LTSearch
		{
		private java.util.List<SearchHit> hits=new ArrayList<SearchHit>();
		
		@Override
		protected void found(LTNode node,Organism organism, Track track, ChromInfo chrom,
				MappedObject o) {
			
			SearchHit hit = new SearchHit();
			hit.organism=organism;
			hit.mapped= o;
			hit.chrom=chrom;
			hit.track=track;
			hit.nodeId=node.getId();
			this.hits.add(hit);
			}
		
		}
	
	private int parseUInt(String s,int defaultValue)
		throws ServletException
		{
		if(s==null || s.trim().length()==0) return defaultValue;
		try
			{
			int i= Integer.parseInt(s.trim());
			if(i<0) throw new ServletException("parameter lower than 0");
			return i;
			}
		catch (NumberFormatException e)
			{
			throw new ServletException("Not a valid number :"+s);
			}
		}
	
	private User createUser()
		{
		User user=new User();
		for(Integer tracks :getModel().getTracksDB().getKeysAsSet(getTransaction()))
			{
			TrackChoice choice= new TrackChoice();
			choice.trackId=tracks;
			user.trackChoices.put(choice.getTrackId(), choice);
			}
		return user;
		}
	
	private void jspDispatch(
			String shortPath,
			HttpServletRequest req, HttpServletResponse res)
		throws IOException,ServletException
		{
		Enumeration<?> e=req.getParameterNames();
		while(e.hasMoreElements())
			{
			String k=(String) e.nextElement();
			if(req.getAttribute(k)==null)
				{
				req.setAttribute(k, req.getParameter(k));
				}
			}
		User user=null;
		HttpSession session= req.getSession(false);
		if(session!=null)
			{
			user=(User)session.getAttribute("user");
			}
		
		if(shortPath.equals("organisms"))
			{
			req.setAttribute("organisms", getModel().getOrganismDB().getValues(getTransaction()));
			}
		else if(shortPath.equals("search"))
			{
			//if(StringUtils.isEmpty(req.getAttribute("whole"))) req.setAttribute("whole","yes");
			//if(req.getAttribute("casei")==null) req.setAttribute("casei","no");
			//if(req.getAttribute("regex")==null) req.setAttribute("regex","no");
			}
		else if(shortPath.equals("browse"))
			{
			Integer orgid=Cast.Integer.cast(req.getParameter("org-id"));
			if(orgid==null) orgid=Cast.Integer.cast(req.getParameter("organism-id"));
			
			if(orgid==null)
				{
				if(user!=null) orgid=user.getOrganismId();
				if(orgid==null)
					{
					jspDispatch("organisms",req,res);
					return;
					}
				}
			
			Organism organism= getModel().getOrganismDB().get(getTransaction(), orgid);
			if(organism==null)
				{
				jspDispatch("organisms",req,res);
				return;
				}
			req.setAttribute("organism",organism); 
			req.setAttribute("chromosomes", getModel().getChromsomesByOrganismId(getTransaction(), organism.getId()));
			req.setAttribute("tracks", getModel().getTracksDB().getValues(getTransaction()));
			}
		req.getRequestDispatcher("/WEB-INF/jsp/"+shortPath+".jsp").forward(req,res);
		}
	
	
	private void dump(
			PrintStream out,
			long nodeid,
			Organism organism,
			Track track,
			ChromInfo ci,
			int start,
			int end) throws IOException,ServletException,DatabaseException
		{
		if(nodeid==LTNode.NIL) return;
		LTNode node= getModel().getLocusNodeDB().get(getTransaction(), nodeid);
		if(node==null) return;//?
		if(node.getStart()>= end) return;
		if(node.getEnd()<= start) return;
		if(node.hasEntities())
			{
			for(MappedObject o:node.getEntities())
				{
				if(o.getStart()>= end) return;
				if(o.getEnd()<= start) return;
				out.print(
					organism.getName()+"\t"+
					ci.getName()+"\t"+
					track.getName()+"\t"+
					o.getId()+"\t"+
					o.getName()+"\t"+
					o.getStart()+"\t"+
					o.getEnd()
					);
				if(o.getJSON()!=null)
					{
					out.print("\t");
					out.print(new JSONBuilder().toString(o.getJSON()));
					}
				out.println();
				}
			}
		if(node.hasSubNodes())
			{
			for(Long sub: node.getSubNodesId())
				{
				dump(out,sub,organism,track,ci,start,end);
				}
			}
		}
	
	/**
	 * Segment for the DAS protocol
	 * @author pierre
	 *
	 */
	private static class Segment
		{
		ChromInfo chrom;
		long start;
		long end;
		}
	
	/**
	 * Implementation of the DAS protocol
	 * @param req
	 * @param res
	 * @throws IOException
	 * @throws ServletException
	 */
	private void runDasProtocol(HttpServletRequest req,HttpServletResponse res)
		throws IOException,ServletException
		{
		String pathInfo= req.getPathInfo();
		String requestURL= req.getRequestURL().toString();
		final String stylesheet="xml-stylesheet type='text/xsl' href='"+req.getContextPath()+"/static/xsl/das2html.xsl'";

		/* Retrieves the list of data sources available from this server  */
		try
			{
			if(pathInfo==null ||
				pathInfo.equals("/dsn"))
				{
				OutputStream out= res.getOutputStream();
		    	XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
		    	XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,"UTF-8");
				res.setContentType( "text/xml");
				
				w.writeStartDocument("UTF-8","1.0");
				w.writeProcessingInstruction(stylesheet);
				w.writeDTD("<!DOCTYPE DASDSN SYSTEM \'http://www.biodas.org/dtd/dasdsn.dtd\'>");
				w.writeStartElement("DASDSN");
				
				for(Organism as: getModel().getOrganismDB().getValues(getTransaction()))
					{
					w.writeStartElement("DSN");
					
					w.writeStartElement("SOURCE");
					w.writeAttribute("id", String.valueOf(as.getName()));
					w.writeAttribute("version", "1.0");
					w.writeCharacters(as.getName());
					w.writeEndElement();
					
					w.writeStartElement("MAPMASTER");
					w.writeCharacters(requestURL.substring(0,requestURL.length()-4)+"/"+as.getName());
					w.writeEndElement();
					
					w.writeStartElement("DESCRIPTION");
					w.writeCharacters(as.getDescription());
					w.writeEndElement();
					
					w.writeEndElement();
					}
				
				w.writeEndElement();
				w.writeEndDocument();
				w.flush();
				w.close();
				}
			else if(pathInfo.matches("/[A-Za-z0-9_]+/dna"))
				{
				int j= pathInfo.indexOf('/',1);
				if(j==-1) throw new ServletException("Bad path");
				String as= pathInfo.substring(1,j);
				if(StringUtils.isEmpty(as))throw new ServletException("Bad assembly");
				Organism organism= getModel().getOrganismByName(getTransaction(),as);
				if(organism==null) throw new ServletException("Bad organism "+as);
							
				List<Segment> segments= getSegments(organism,req);
				if(segments.isEmpty()) throw new ServletException("One segment required");
				
				
				OutputStream out= res.getOutputStream();
		    	XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
		    	XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,"UTF-8");
		    	res.setContentType( "text/xml");
				
				w.writeStartDocument("UTF-8","1.0");
				w.writeProcessingInstruction(stylesheet);
				w.writeDTD("<!DOCTYPE DASDNA SYSTEM \"http://www.biodas.org/dtd/dasdna.dtd\">");
				w.writeStartElement("DASDNA");
				w.writeComment("NO IMPLEMENTED");
				
				
				
				for(Segment seg:segments)
					{
					w.writeStartElement("SEQUENCE");
					w.writeAttribute("id",seg.chrom.getName());
					w.writeAttribute("start",String.valueOf(1+seg.start));
					w.writeAttribute("stop",String.valueOf(1+seg.end));
					w.writeAttribute("version","1.0");
					
					w.writeStartElement("DNA");
					w.writeAttribute("length",String.valueOf((seg.end-seg.start)+1));
					
					final int buff=100000;
					StringBuilder b= new StringBuilder(100000);
					while(true)
						{
						while(seg.start<=seg.end && b.length()< buff)
							{
							b.append("N");
							seg.start++;
							}
						if(b.length()==0) break;
						w.writeCharacters(b.toString());//TODO
						w.writeCharacters("\n");
						b.setLength(0);
						}
						
					
					w.writeEndElement();
					
					w.writeEndElement();
					}
				
				
				
				w.writeEndElement();
				w.writeEndDocument();
				w.flush();
				w.close();
				}
			else if(pathInfo.matches("/[A-Za-z0-9_]+/types"))
				{
				int j= pathInfo.indexOf('/',1);
				if(j==-1) throw new ServletException("Bad path");
				String as= pathInfo.substring(1,j);
				if(StringUtils.isEmpty(as))throw new ServletException("Bad organism");
				Organism organism= getModel().getOrganismByName(getTransaction(),as);
				if(organism==null) throw new ServletException("Bad assembly");

				List<Segment> segments = getSegments(organism, req);
			
				
				OutputStream out= res.getOutputStream();
		    	XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
		    	XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,"UTF-8");
		    	res.setContentType( "text/xml");
				
				w.writeStartDocument("UTF-8","1.0");
				w.writeProcessingInstruction(stylesheet);
				w.writeDTD("<!DOCTYPE DASTYPES SYSTEM \"http://www.biodas.org/dtd/dastypes.dtd\">");
				w.writeStartElement("DASTYPES");
				w.writeStartElement("GFF");
				w.writeAttribute("href",requestURL+(req.getQueryString()==null?"":"?"+req.getQueryString()));
				w.writeAttribute("version","1.0");
				
				for(ChromInfo ci: getModel().getChromsomesByOrganismId(getTransaction(), organism.getId()))
					{
					boolean found=false;
					if(segments.isEmpty())
						{
						found=true;
						}
					else
						{
						for(Segment seg: segments)
							{
							if(seg.chrom.getId().equals(ci.getId()))
								{
								found=true;
								break;
								}
							}
						}
					if(!found) continue;
					
					w.writeStartElement("SEGMENT");
					w.writeAttribute("version","1.0");
					w.writeAttribute("id",ci.getName());
					w.writeAttribute("start","1");
					w.writeAttribute("stop",String.valueOf(1+ci.getLength()));
					w.writeAttribute("label",ci.getName());
					
					for(Track track:getModel().getTracksDB().getValues(getTransaction()))
						{
						if(!getModel().getTrackChrom2NodeId().containsKey(getTransaction(), new TrackChrom(track,ci)))
							{
							continue;
							}
						w.writeStartElement("TYPE");
						w.writeAttribute("id",String.valueOf(track.getId()));
						w.writeAttribute("label",track.getName());
						w.writeAttribute("method","");
						w.writeAttribute("category","other");
						w.writeCharacters(track.getDescription());
						w.writeEndElement();
						}
					w.writeEndElement();
					}
				w.writeEndElement();
				w.writeEndDocument();
				w.flush();
				w.close();
				}
			
			else if(pathInfo.matches("/[A-Za-z0-9_]+/entry_points"))
				{
				int j= pathInfo.indexOf('/',1);
				if(j==-1) throw new ServletException("Bad path");
				String as= pathInfo.substring(1,j);
				if(StringUtils.isEmpty(as))throw new ServletException("Bad organism");
				Organism organism= getModel().getOrganismByName(getTransaction(),as);
				if(organism==null) throw new ServletException("Bad organism");
				
		
				
				OutputStream out= res.getOutputStream();
		    	XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
		    	XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,"UTF-8");
		    	res.setContentType( "text/xml");
				
				w.writeStartDocument("UTF-8","1.0");
				w.writeProcessingInstruction(stylesheet);
				w.writeDTD("<!DOCTYPE DASEP SYSTEM \'http://www.biodas.org/dtd/dasep.dtd\'>");
				w.writeStartElement("DASEP");
				w.writeStartElement("ENTRY_POINTS");
				w.writeAttribute("href",requestURL);
				w.writeAttribute("version","1.0");
				
				
				for(ChromInfo ci: getModel().getChromsomesByOrganismId(getTransaction(), organism.getId()))
					{
					w.writeStartElement("SEGMENT");
					w.writeAttribute("id",ci.getName());
					w.writeAttribute("start",String.valueOf(1));
					w.writeAttribute("stop",String.valueOf(1+ci.getLength()));
					w.writeAttribute("orientation","+");
					w.writeAttribute("subparts","no");
					w.writeCharacters(ci.getName());
					w.writeEndElement();
					}
				
				w.writeEndElement();
				w.writeEndElement();
				w.writeEndDocument();
				w.flush();
				w.close();
				}
			
			else if(pathInfo.matches("/[A-Za-z0-9_]+/features") )
				{
				int j= pathInfo.indexOf('/',1);
				if(j==-1) throw new ServletException("Bad path");
				String as= pathInfo.substring(1,j);
				if(StringUtils.isEmpty(as))throw new ServletException("Bad organism");
				Organism organism= getModel().getOrganismByName(getTransaction(),as);
				if(organism==null) throw new ServletException("Bad organism");
				
				
				
				List<Segment> segments= getSegments(organism,req);
				if(segments==null) segments= new ArrayList<Segment>();
				String types[]= req.getParameterValues("type");
				if(types==null) types=new String[0];
				Set<String> typeset= new HashSet<String>(Arrays.asList(types));
				
				OutputStream out= res.getOutputStream();
		    	XMLOutputFactory xmlfactory= XMLOutputFactory.newInstance();
		    	XMLStreamWriter w= xmlfactory.createXMLStreamWriter(out,"UTF-8");
		    	res.setContentType( "text/xml");
				
				w.writeStartDocument("UTF-8","1.0");
				w.writeProcessingInstruction(stylesheet);
				w.writeDTD("<!DOCTYPE DASGFF SYSTEM \"http://www.biodas.org/dtd/dasgff.dtd\">");
				w.writeStartElement("DASGFF");
				w.writeStartElement("GFF");
				w.writeAttribute("href",requestURL+(req.getQueryString()==null?"":"?"+req.getQueryString()));
				w.writeAttribute("version","1.0");
				
				
				for(Segment seg:segments)
					{
					w.writeStartElement("SEGMENT");
					w.writeAttribute("id",seg.chrom.getName());
					w.writeAttribute("label",seg.chrom.getName());
					w.writeAttribute("start",String.valueOf(1+seg.start));
					w.writeAttribute("stop",String.valueOf(1+seg.end));
					w.writeAttribute("version","1.0");
					
					for(Track track:getModel().getTracksDB().getValues(getTransaction()))
						{
						if(!typeset.isEmpty() && !typeset.contains(String.valueOf(track.getId()))) continue;
						Long nodeId=getModel().getTrackChrom2NodeId().get(
								getTransaction(),new TrackChrom(track,seg.chrom));
						
						if(nodeId==null) continue;
						das(w, nodeId, organism, track, seg);
						}
					
					w.writeEndElement();
					}
				
				w.writeEndElement();
				w.writeEndElement();
				w.writeEndDocument();
				w.flush();
				w.close();
				}
			else
				{
				throw new ServletException("Illegal Path");
				}
			}
		catch (Exception e) {
		// TODO: handle exception
			}
		}
	
	private List<Segment> getSegments(Organism organism,HttpServletRequest req) throws ServletException
		{
		List<Segment> list= new ArrayList<Segment>();
		String param[]=req.getParameterValues("segment");
		if(param==null) return list;
		
		List<String> array= new ArrayList<String>(param.length);
		for(String s:param)
			{
			for(String s2: s.split("[;]")) array.add(s2.trim());
			}
		
		for(String s:array)
			{
			Segment seg= new Segment();
			int i=s.indexOf(':');
			if(i<1)
				{
				String s2= s;
				if(StringUtils.isEmpty(s2))  throw new ServletException("Illegal segment"+s);
				seg.chrom= getModel().getChromsomeByName(getTransaction(), organism.getId(), s2);
				if(seg.chrom==null )  throw new ServletException("Illegal segment:"+s);
				seg.start=0L;
				seg.end=seg.chrom.getLength();
				list.add(seg);
				continue;
				}
			
			
			if(i<1 || i+1==s.length()) throw new ServletException("Illegal segment:"+s);
			int j= s.indexOf(',',i);
			if(j==-1 || j+1==s.length()) throw new ServletException("Illegal segment:"+s);
			String s2= s.substring(0,i);
			if(StringUtils.isEmpty(s2))  throw new ServletException("Illegal segment:"+s);
			seg.chrom= getModel().getChromsomeByName(getTransaction(), organism.getId(), s2);
			if(seg.chrom==null)  throw new ServletException("Illegal segment"+s);
			 s2= s.substring(i+1,j);
			if(!Cast.Integer.isA(s2))  throw new ServletException("Illegal segment"+s);
			seg.start= Cast.Integer.cast(s2)-1;
			if(seg.start<0 || seg.start==Integer.MAX_VALUE)  throw new ServletException("Illegal segment"+s);
			s2= s.substring(j+1);
			if(!Cast.Integer.isA(s2))  throw new ServletException("Illegal segment"+s);
			seg.end= Cast.Integer.cast(s2)-1;
			if(seg.end<0 || seg.end==Integer.MAX_VALUE)  throw new ServletException("Illegal segment"+s);
			list.add(seg);
			}
		return list;
		}
	
	
	private void das(
			XMLStreamWriter out,
			long nodeid,
			Organism organism,
			Track track,
			Segment seg
			) throws IOException,ServletException,DatabaseException,XMLStreamException
		{
		if(nodeid==LTNode.NIL) return;
		LTNode node= getModel().getLocusNodeDB().get(getTransaction(), nodeid);
		if(node==null) return;//?
		if(node.getStart()>= seg.end) return;
		if(node.getEnd()<= seg.start) return;
		if(node.hasEntities())
			{
			for(MappedObject o:node.getEntities())
				{
				if(o.getStart()>= seg.end) return;
				if(o.getEnd()<= seg.start) return;
				writeDasFeature(out,track,seg.chrom,node,o);
				}
			}
		if(node.hasSubNodes())
			{
			for(Long sub: node.getSubNodesId())
				{
				das(out,sub,organism,track,seg);
				}
			}
		}
	
	/* echo a DAS Feature */
	private void writeDasFeature(
		XMLStreamWriter w,
		Track track,
		ChromInfo ci,
		LTNode node,
		MappedObject mapped
		) throws XMLStreamException
		{
		w.writeStartElement("FEATURE");
		w.writeAttribute("id",mapped.getId());
		w.writeAttribute("label",mapped.getName());
		
		w.writeStartElement("TYPE");
		w.writeAttribute("id",String.valueOf(track.getId()));
		w.writeAttribute("category","");
		w.writeAttribute("reference","no");
		w.writeCharacters(track.getName());
		w.writeEndElement();
		
		
		w.writeStartElement("METHOD");
		w.writeEndElement();
		
		w.writeStartElement("START");
		w.writeCharacters(String.valueOf(1+mapped.getStart()));
		w.writeEndElement();
		
		w.writeStartElement("END");
		w.writeCharacters(String.valueOf(1+mapped.getEnd()));
		w.writeEndElement();
		
		w.writeStartElement("SCORE");
		w.writeCharacters("-");
		w.writeEndElement();
		
		w.writeStartElement("ORIENTATION");
		w.writeCharacters("0");
		w.writeEndElement();
		
		
		w.writeStartElement("PHASE");
		w.writeCharacters("-");
		w.writeEndElement();
		
		Map<String,Object> json=mapped.getJSON();
		if(json!=null)
			{
			try
				{
				String note=new JSONBuilder().toString(json);
				w.writeStartElement("NOTE");
				w.writeCharacters(note);
				w.writeEndElement();
				}
			catch (Exception e)
				{
				w.writeComment("error:"+e.getMessage());
				}
		
			}
		w.writeEndElement();
		}
	
	
	}
